<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Spring 的事务 | 梓琰苑</title><meta name="keywords" content="Spring"><meta name="author" content="Milk Yang"><meta name="copyright" content="Milk Yang"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="[TOC] Spring 的事务Spring支持的事务策略Java EE应用的传统事务有两种策略：全局事务和局部事务。全局事务由应用服务器管理，需要底层服务器的JTA(全称Java Transaction API ，即Java事务API)。局部事务和底层所采用的持久化技术相关，当采用JDBC持久化技术时，需要使用Connection对象来操作事务；而采用Hibernate持久化技术时，需要使用Se">
<meta property="og:type" content="article">
<meta property="og:title" content="Spring 的事务">
<meta property="og:url" content="http://yzb317415.gitee.io/blog/2022/06/22/Spring%E7%9A%84%E4%BA%8B%E5%8A%A1/index.html">
<meta property="og:site_name" content="梓琰苑">
<meta property="og:description" content="[TOC] Spring 的事务Spring支持的事务策略Java EE应用的传统事务有两种策略：全局事务和局部事务。全局事务由应用服务器管理，需要底层服务器的JTA(全称Java Transaction API ，即Java事务API)。局部事务和底层所采用的持久化技术相关，当采用JDBC持久化技术时，需要使用Connection对象来操作事务；而采用Hibernate持久化技术时，需要使用Se">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="">
<meta property="article:published_time" content="2022-06-22T10:06:45.819Z">
<meta property="article:modified_time" content="2018-11-05T09:29:19.000Z">
<meta property="article:author" content="Milk Yang">
<meta property="article:tag" content="Spring">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content=""><link rel="shortcut icon" href="/blog/img/favicon.png"><link rel="canonical" href="http://yzb317415.gitee.io/blog/2022/06/22/Spring%E7%9A%84%E4%BA%8B%E5%8A%A1/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/blog/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/blog/',
  algolia: undefined,
  localSearch: undefined,
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery@2/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Spring 的事务',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2018-11-05 17:29:19'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><meta name="generator" content="Hexo 6.2.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">8</div></a><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">2</div></a><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">1</div></a></div><hr/></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/blog/">梓琰苑</a></span><div id="menus"><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">Spring 的事务</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-06-22T10:06:45.819Z" title="发表于 2022-06-22 18:06:45">2022-06-22</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2018-11-05T09:29:19.000Z" title="更新于 2018-11-05 17:29:19">2018-11-05</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/blog/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">学习笔记</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Spring 的事务"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><p>[TOC]</p>
<h1 id="Spring-的事务"><a href="#Spring-的事务" class="headerlink" title="Spring 的事务"></a>Spring 的事务</h1><h2 id="Spring支持的事务策略"><a href="#Spring支持的事务策略" class="headerlink" title="Spring支持的事务策略"></a>Spring支持的事务策略</h2><p>Java EE应用的传统事务有两种策略：全局事务和局部事务。全局事务由应用服务器管理，需要底层服务器的JTA(全称Java Transaction API ，即Java事务API)。局部事务和底层所采用的持久化技术相关，当采用JDBC持久化技术时，需要使用Connection对象来操作事务；而采用Hibernate持久化技术时，需要使用Session对象来操作事务。</p>
<ol>
<li><p>术语</p>
<blockquote>
<ul>
<li>全局事务：Global Transactions</li>
<li>局部事务：Local Transactions</li>
<li>JTA：Java Transaction API(Java事务接口)</li>
<li>CMT：Container Managed Transaction(容器管理事务)</li>
<li>声明式事务管理：Declarative Transaction Management</li>
<li>编程式事务管理：Programmatic Transaction Management</li>
<li>JNDI：Java Naming and Directory Interface(Java命名和目录接口)</li>
</ul>
</blockquote>
</li>
<li><p>全局事务<br> 全局事务由应用服务器通过JTA进行管理。以前，使用全局事务比较流行的方法是采用EJB CMT，CMT是声明式事务管理的一种形式（区别于编程式事务管理）。尽管使用EJB本身就需要使用JNDI ，EJB CMT不需要事务相关的JNDI lookups。EJB CMT不需要编写大量的Java代码来控制事务。使用CMT最大的不足之处是它被束缚在JTA和应用服务器上。CMT只有当在EJB中实现业务逻辑时才可以发挥作用，或者至少处于事务性EJB Façade中。由于EJB存在诸多的不足并且目前存在其他可选的声明式事务管理的解决方案，所以不太建议使用EJB方式。</p>
<ol>
<li>全局事务的优点：<ul>
<li>全局事务支持多个事务性资源间的相互工作（如：关系型数据库和消息队列）。</li>
</ul>
</li>
<li>全局事务的缺点：<ol>
<li>采用全局事务需要使用JTA，而JTA是一个笨重的API。</li>
<li>通常情况下，JTA UserTransaction需要从JNDI获取。这意味着，如果我们使用JTA，就需要同时使用JTA和JNDI。</li>
<li>通常JTA只能在应用服务器环境下使用，因此使用JTA会限制代码的复用性。</li>
</ol>
</li>
</ol>
</li>
<li><p>局部事务<br> 局部事务是资源特有的（resource-specific），最常见的例子是与JDBC连接相关联的事务。使用局部事务，应用服务器不参与事务管理并且不能保证访问多个资源的正确性。值得注意的是：大多数应用程序使用单个的事务性资源。   </p>
<ol>
<li>局部事务的优点：<ul>
<li>易用</li>
</ul>
</li>
<li>局部事务的缺点：<ol>
<li>局部事务不支持多个事务性资源间的相互工作，比如：使用JDBC连接来管理事务的代码不能在全局JTA事务上运行。</li>
<li>局部事务趋向于编程模型，编程模型具有侵入性。</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>Spring可以使应用程序开发者在任何环境下使用统一的编程模型。应用程序开发者只需要编写一次代码，就可以使用不同环境下的不同事务管理策略。Spring既支持声明式事务管理，也支持编程式事务管理。</p>
<p>我们推荐使用声明式事务管理。采用编程式事务管理，开发者需要使用Spring提供的事务抽象（它可以在任何基础的事务架构上运行）。采用声明式事务管理，开发者仅需要编写少量甚至不需要编写与事务管理相关的代码，因此它不依赖于Spring的事务API。</p>
<p>Spring事务策略是通过PlatformTransactionManage接口体现的，该接口是Spring事务策略的核心，该接口定义如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">PlatformTransactionManage</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 平台无关的获得事务的方法</span></span><br><span class="line">    TransactionStatus <span class="title function_">getTransaction</span><span class="params">(TransactionDefinition definition)</span> </span><br><span class="line">    <span class="keyword">throws</span> TransactionException;</span><br><span class="line">    <span class="comment">//平台无关的事务提交方法</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">commit</span><span class="params">(TransactionStatus status)</span> <span class="keyword">throws</span> TransactionException;</span><br><span class="line">    <span class="comment">//平台无关的事务回滚方法</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">rollback</span><span class="params">(TransactionStatus status)</span> <span class="keyword">throws</span> TransactionException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>PlatformTransactionManage是一个与任何事务策略分离的接口，随着低层不同事务策略的切换，应用必须采用不同的实现类。</p>
<p>PlatformTransactionManage接口有许多不同的实现类，应用程序面向与平台无关的接口编程，当底层采用不同的持久层技术时，系统只需使用不同的PlatformTransactionManage实现类即可————而这种切换通常由Spring容器负责管理，应用程序既无须与具体的事务API耦合，也无须与特定实现类耦合，从而将应用和持久化技术、事务API彻底分离处理。</p>
<p>Spring的事务机制是一种典型的策略模式，PlatformTransactionManage代表事务管理接口，但它并不知道底层到底如何管理事务，它只要求事务管理需要提供三个事务方法，但具体如何实现则交给其实现类来完成————不同实现则代表不同的事务管理策略。</p>
<p>即使使用容器管理的JTA，代码也依然无须指定JNDI查找，无须与特定的JTA资源耦合在一起，通过配置文件，JTA资源传给PlatformTransactionManage的实现类。因此，程序的代码可在JTA事务管理和非JTA事务管理之间轻松切换。</p>
<hr>
<p>在PlatformTransactionManage接口内，包含一个<code>getTransaction(TransactionDefinition definition)</code>方法，该方法根据TransactionDefinition参数返回一个TransactionStatus对象。TransactionStatus对象表示一个事务，TransactionStatus被关联在当前执行的线程上。</p>
<p><code>getTransaction(TransactionDefinition definition)</code>返回的TransactionStatus对象，可能是一个新的事务，也可能是一个已经存在的事务对象。如果当前执行的线程已经处于事务管理下，则返回当前线程的事务对象；否则，系统将新建一个事务对象后返回。</p>
<p>TransactionDefinition接口定义了事务规则，该接口必须制定如下几个属性值：</p>
<blockquote>
<ul>
<li><p>事务隔离：当前事务和其他事务的隔离程度</p>
<ol>
<li>TransactionDefinition.ISOLATION_DEFAULT<br> 这是默认值，表示使用底层数据库的默认隔离级别。对大部分数据库而言，通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED</li>
<li>TransactionDefinition.ISOLATION_READ_UNCOMMITTED<br> 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读，不可重复读和幻读，因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。</li>
<li>TransactionDefinition.ISOLATION_READ_COMMITTED<br> 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读，这也是大多数情况下的推荐值。</li>
<li>TransactionDefinition.ISOLATION_REPEATABLE_READ<br> 该隔离别表示一个事务在整个过程中可以多次重复执行某个查询，并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。</li>
<li>TransactionDefinition.ISOLATION_SERIALIZABLE<br> 所有的事务依次逐个执行，这样事务之间就完全不可能产生干扰，也就是说，该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。</li>
</ol>
<blockquote>
<p>MYSQL: 默认为REPEATABLE_READ级别<br>SQLSERVER: 默认为READ_COMMITTED</p>
<ul>
<li>脏读 ：一个事务读取到另一事务未提交的更新数据</li>
<li>不可重复读 ：在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说, 后续读取可以读到另一事务已提交的更新数据. 相反, “可重复读”在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据</li>
<li>幻读 ：一个事务读到另一个事务已提交的insert数据。</li>
</ul>
</blockquote>
</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li><p>事务传播：通常，在事务中执行的代码都会在当前事务中运行</p>
<ol>
<li>TransactionDefinition.PROPAGATION_REQUIRED<br>如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。这是默认值。</li>
<li>TransactionDefinition.PROPAGATION_REQUIRES_NEW<br>创建一个新的事务，如果当前存在事务，则把当前事务挂起</li>
<li>TransactionDefinition.PROPAGATION_SUPPORTS<br>如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行。</li>
<li>TransactionDefinition.PROPAGATION_NOT_SUPPORTED<br>以非事务方式运行，如果当前存在事务，则把当前事务挂起。</li>
<li>TransactionDefinition.PROPAGATION_NEVER<br>以非事务方式运行，如果当前存在事务，则抛出异常。</li>
<li>TransactionDefinition.PROPAGATION_MANDATORY<br>如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。</li>
<li>TransactionDefinition.PROPAGATION_MANDATORY<br>如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。</li>
</ol>
</li>
<li><p>事务超时：指一个事务所允许执行的最长时间，如果超过该时间限制但事务还没有完成，则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间，其单位是秒。默认设置为底层事务系统的超时值，如果底层数据库事务系统没有设置超时值，那么就是none，没有超时限制。</p>
</li>
<li><p>事务只读属性：只读事务用于客户代码只读但不修改数据的情形，只读事务用于特定情景下的优化，比如使用Hibernate的时候。默认为读写事务</p>
</li>
</ul>
</blockquote>
<p>TransactionStatus代表事务本省，它提供了简单的控制事务执行和查询事务状态的方法，这些方法在所有事务API中都是相同的。TransactionStatus接口的定义如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">TransactionStatus</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 判断事务是否为新建的事务</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">isNewTransaction</span><span class="params">()</span>;</span><br><span class="line">    <span class="comment">// 设置事务回滚</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setRollbackOnly</span><span class="params">()</span>;</span><br><span class="line">    <span class="comment">// 查询事务是否已有回滚标志</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">isRollbackOnly</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="使用XML-Schema配置事务策略"><a href="#使用XML-Schema配置事务策略" class="headerlink" title="使用XML Schema配置事务策略"></a>使用XML Schema配置事务策略</h2><p>Spring提供了tx:命名空间来配置事务管理，tx:命名空间下提供了<code>&lt;tx:advice.../&gt;</code>元素来配置事务增强处理，一旦使用该元素配置了事务管理处理，就可以直接使用<code>&lt;aop:advisor.../&gt;</code>元素启用自动代理了。</p>
<p>配置<code>&lt;tx:advice.../&gt;</code>时除了需要transaction-manager属性指定事务管理器之外，还需要配置一个<code>&lt;attributes.../&gt;</code>子元素，该子元素里又可包含多个<code>&lt;method.../&gt;</code>子元素。</p>
<p><code>&lt;method.../&gt;</code>子元素可以指定如下属性：</p>
<blockquote>
<p>name：必选属性，与该事务语义关联的方法名。该属性支持通配符<br>propagation：指定事务传播行为<br>isolation：指定事务隔离级别<br>timeout：指定事务超时时间<br>read-only：指定事务是否只读<br>rollback-for：指定触发时间回滚的异常类，该属性可指定多个异常类，多个异常类用逗号隔开<br>no-rollback-for：指定不触发时间回滚的一场了，该属性可指定多个异常类，多个异常类用逗号隔开</p>
</blockquote>
<p><code>&lt;aop:advisor&gt;</code>作用：将Advice和切入点绑定在一起，保证Advice所包含的增强处理将在对应的切入点被织入。当采用<code>&lt;aop:advisor&gt;</code>元素将Advice和切入点绑定时，实际上是由Spring提供的Bean后处理器完成的。Spring提供了BeanNameAutoProxyCreator、DefaultAdvisorAutoProxyCreator两个Bean后处理器。</p>
<p>实例代码：<br>NewsDaoImpl组件包含一个insert()方法，该方法同时插入两条记录，但插入的第二条记录将会违反唯一约束，从而应发异常</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewsDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">NewsDao</span></span><br><span class="line">&#123;</span><br><span class="line">	<span class="keyword">private</span> DataSource ds;</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDs</span><span class="params">(DataSource ds)</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">this</span>.ds = ds;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(String title, String content)</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="type">JdbcTemplate</span> <span class="variable">jt</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JdbcTemplate</span>(ds);</span><br><span class="line">		jt.update(<span class="string">&quot;insert into news_inf&quot;</span></span><br><span class="line">			+ <span class="string">&quot; values(null , ? , ?)&quot;</span></span><br><span class="line">			, title , content);</span><br><span class="line">		<span class="comment">// 两次插入的数据违反唯一键约束</span></span><br><span class="line">		jt.update(<span class="string">&quot;insert into news_inf&quot;</span></span><br><span class="line">			+ <span class="string">&quot; values(null , ? , ?)&quot;</span></span><br><span class="line">			, title , content);</span><br><span class="line">		<span class="comment">// 如果没有事务控制，则第一条记录可以被插入</span></span><br><span class="line">		<span class="comment">// 如果增加事务控制，将发现第一条记录也插不进去。</span></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>配置文件：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">beans</span> <span class="attr">xmlns:xsi</span>=<span class="string">&quot;http://www.w3.org/2001/XMLSchema-instance&quot;</span></span></span><br><span class="line"><span class="tag">	<span class="attr">xmlns</span>=<span class="string">&quot;http://www.springframework.org/schema/beans&quot;</span></span></span><br><span class="line"><span class="tag">	<span class="attr">xmlns:p</span>=<span class="string">&quot;http://www.springframework.org/schema/p&quot;</span></span></span><br><span class="line"><span class="tag">	<span class="attr">xmlns:aop</span>=<span class="string">&quot;http://www.springframework.org/schema/aop&quot;</span></span></span><br><span class="line"><span class="tag">	<span class="attr">xmlns:tx</span>=<span class="string">&quot;http://www.springframework.org/schema/tx&quot;</span></span></span><br><span class="line"><span class="tag">	<span class="attr">xsi:schemaLocation</span>=<span class="string">&quot;http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="string"><span class="tag">	http://www.springframework.org/schema/beans/spring-beans.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag">	http://www.springframework.org/schema/aop</span></span></span><br><span class="line"><span class="string"><span class="tag">	http://www.springframework.org/schema/aop/spring-aop.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag">	http://www.springframework.org/schema/tx</span></span></span><br><span class="line"><span class="string"><span class="tag">	http://www.springframework.org/schema/tx/spring-tx.xsd&quot;</span>&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- 定义数据源Bean，使用C3P0数据源实现，并注入数据源的必要信息 --&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;dataSource&quot;</span> <span class="attr">class</span>=<span class="string">&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">destroy-method</span>=<span class="string">&quot;close&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:driverClass</span>=<span class="string">&quot;com.mysql.jdbc.Driver&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:jdbcUrl</span>=<span class="string">&quot;jdbc:mysql://localhost/spring?useSSL=true&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:user</span>=<span class="string">&quot;root&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:password</span>=<span class="string">&quot;32147&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:maxPoolSize</span>=<span class="string">&quot;40&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:minPoolSize</span>=<span class="string">&quot;2&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:initialPoolSize</span>=<span class="string">&quot;2&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:maxIdleTime</span>=<span class="string">&quot;30&quot;</span>/&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- 配置JDBC数据源的局部事务管理器，使用DataSourceTransactionManager 类 --&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- 该类实现PlatformTransactionManager接口，是针对采用数据源连接的特定实现--&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- 配置DataSourceTransactionManager时需要依注入DataSource的引用 --&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;transactionManager&quot;</span> </span></span><br><span class="line"><span class="tag">		<span class="attr">class</span>=<span class="string">&quot;org.springframework.jdbc.datasource.DataSourceTransactionManager&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:dataSource-ref</span>=<span class="string">&quot;dataSource&quot;</span>/&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- 配置一个业务逻辑Bean --&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">bean</span> <span class="attr">id</span>=<span class="string">&quot;newsDao&quot;</span> <span class="attr">class</span>=<span class="string">&quot;org.crazyit.app.dao.impl.NewsDaoImpl&quot;</span></span></span><br><span class="line"><span class="tag">		<span class="attr">p:ds-ref</span>=<span class="string">&quot;dataSource&quot;</span>/&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- 配置事务增强处理Bean,指定事务管理器 --&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">tx:advice</span> <span class="attr">id</span>=<span class="string">&quot;txAdvice&quot;</span> </span></span><br><span class="line"><span class="tag">		<span class="attr">transaction-manager</span>=<span class="string">&quot;transactionManager&quot;</span>&gt;</span></span><br><span class="line">		<span class="comment">&lt;!-- 用于配置详细的事务语义 --&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">tx:attributes</span>&gt;</span></span><br><span class="line">			<span class="comment">&lt;!-- 所有以&#x27;get&#x27;开头的方法是read-only的 --&gt;</span></span><br><span class="line">			<span class="tag">&lt;<span class="name">tx:method</span> <span class="attr">name</span>=<span class="string">&quot;get*&quot;</span> <span class="attr">read-only</span>=<span class="string">&quot;true&quot;</span> <span class="attr">timeout</span>=<span class="string">&quot;8&quot;</span>/&gt;</span></span><br><span class="line">			<span class="comment">&lt;!-- 其他方法使用默认的事务设置，指定超时时长为5秒 --&gt;</span></span><br><span class="line">			<span class="tag">&lt;<span class="name">tx:method</span> <span class="attr">name</span>=<span class="string">&quot;*&quot;</span> <span class="attr">isolation</span>=<span class="string">&quot;DEFAULT&quot;</span></span></span><br><span class="line"><span class="tag">				<span class="attr">propagation</span>=<span class="string">&quot;REQUIRED&quot;</span> <span class="attr">timeout</span>=<span class="string">&quot;5&quot;</span>/&gt;</span></span><br><span class="line">		<span class="tag">&lt;/<span class="name">tx:attributes</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">tx:advice</span>&gt;</span></span><br><span class="line">	<span class="comment">&lt;!-- AOP配置的元素 --&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">aop:config</span>&gt;</span></span><br><span class="line">		<span class="comment">&lt;!-- 配置一个切入点，匹配org.crazyit.app.dao.impl包下</span></span><br><span class="line"><span class="comment">			所有以Impl结尾的类里、所有方法的执行 --&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">aop:pointcut</span> <span class="attr">id</span>=<span class="string">&quot;myPointcut&quot;</span></span></span><br><span class="line"><span class="tag">			<span class="attr">expression</span>=<span class="string">&quot;execution(* org.crazyit.app.dao.impl.*Impl.*(..))&quot;</span>/&gt;</span></span><br><span class="line">		<span class="comment">&lt;!-- 指定在myPointcut切入点应用txAdvice事务增强处理 --&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">aop:advisor</span> <span class="attr">advice-ref</span>=<span class="string">&quot;txAdvice&quot;</span> </span></span><br><span class="line"><span class="tag">			<span class="attr">pointcut-ref</span>=<span class="string">&quot;myPointcut&quot;</span>/&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">aop:config</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">beans</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>运行上面的程序，将出现一个异常，而且insert()方法所执行的两条SQL语句全部回滚</p>
<h2 id="使用-Transactional"><a href="#使用-Transactional" class="headerlink" title="使用@Transactional"></a>使用@Transactional</h2><p>@Transactional可用于修饰Spring Bean类，也可用于修饰Bean类中的某个方法。如果使用@Transactional修饰Bean类，则表明这些食物设置对整个Bean起作用；如果使用@Transactional修饰Bean类的某个方法，则表明这些事务设置只对该方法有效。</p>
<p>使用@Transactional可指定如下属性：</p>
<blockquote>
<ul>
<li>isolation：用于指定事务的隔离级别。默认为底层事务的隔离级别</li>
<li>noRollbackFor：指定遇到特定异常时强制不回滚事务</li>
<li>noRollbackForClassName：指定遇到特定的多个异常时强制不回滚事务。该属性值可以指定多个异常名。</li>
<li>propagation：指定事务传播行为</li>
<li>readonly：指定事务是否只读</li>
<li>rollbackFor：指定遇到特定时强制回滚事务</li>
<li>rollbackForClassName：指定遇到特定的多个异常时强制回滚事务。该属性值可以指定多个异常名。</li>
<li>timeout：指定事务的超时时间</li>
</ul>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewsDaoImpl</span> <span class="keyword">implements</span> <span class="title class_">NewsDao</span></span><br><span class="line">&#123;</span><br><span class="line">	<span class="keyword">private</span> DataSource ds;</span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setDs</span><span class="params">(DataSource ds)</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="built_in">this</span>.ds = ds;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="meta">@Transactional(propagation=Propagation.REQUIRED ,</span></span><br><span class="line"><span class="meta">		isolation=Isolation.DEFAULT , timeout=5)</span></span><br><span class="line">	<span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">insert</span><span class="params">(String title, String content)</span></span><br><span class="line">	&#123;</span><br><span class="line">		<span class="type">JdbcTemplate</span> <span class="variable">jt</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">JdbcTemplate</span>(ds);</span><br><span class="line">		jt.update(<span class="string">&quot;insert into news_inf&quot;</span></span><br><span class="line">			+ <span class="string">&quot; values(null , ? , ?)&quot;</span></span><br><span class="line">			, title , content);</span><br><span class="line">		<span class="comment">// 两次插入的数据违反唯一键约束</span></span><br><span class="line">		jt.update(<span class="string">&quot;insert into news_inf&quot;</span></span><br><span class="line">			+ <span class="string">&quot; values(null , ? , ?)&quot;</span></span><br><span class="line">			, title , content);</span><br><span class="line">		<span class="comment">// 如果没有事务控制，则第一条记录可以被插入</span></span><br><span class="line">		<span class="comment">// 如果增加事务控制，将发现第一条记录也插不进去。</span></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>还需要在xml中添加如下配置<br><code>&lt;tx:annotation-driven transaction-manager=&quot;transactionManager&quot;/&gt;</code></p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="http://yzb317415.gitee.io/blog">Milk Yang</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="http://yzb317415.gitee.io/blog/2022/06/22/Spring%E7%9A%84%E4%BA%8B%E5%8A%A1/">http://yzb317415.gitee.io/blog/2022/06/22/Spring%E7%9A%84%E4%BA%8B%E5%8A%A1/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="http://yzb317415.gitee.io/blog" target="_blank">梓琰苑</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/blog/tags/Spring/">Spring</a></div><div class="post_share"><div class="social-share" data-image="" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/social-share.js/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/gh/overtrue/share.js@master/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/blog/2022/06/22/Spring%E5%85%A5%E9%97%A8/"><img class="prev-cover" src="" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Spring入门</div></div></a></div><div class="next-post pull-right"><a href="/blog/2022/06/22/Spring%E7%9A%84%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/"><img class="next-cover" src="" onerror="onerror=null;src='/blog/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Spring的缓存机制</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/blog/2022/06/22/SpringAOP/" title="SpringAOP"><img class="cover" src="" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-06-22</div><div class="title">SpringAOP</div></div></a></div><div><a href="/blog/2022/06/22/Spring%E5%85%A5%E9%97%A8/" title="Spring入门"><img class="cover" src="" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-06-22</div><div class="title">Spring入门</div></div></a></div><div><a href="/blog/2022/06/22/Spring%E7%9A%84%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6/" title="Spring的缓存机制"><img class="cover" src="" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-06-22</div><div class="title">Spring的缓存机制</div></div></a></div><div><a href="/blog/2022/06/22/%E6%B7%B1%E5%85%A5%E4%BD%BF%E7%94%A8%20Spring/" title="深入使用 Spring"><img class="cover" src="" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-06-22</div><div class="title">深入使用 Spring</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src="https://i.loli.net/2021/02/24/5O1day2nriDzjSu.png" onerror="this.onerror=null;this.src='/blog/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">Milk Yang</div><div class="author-info__description">不积跬步无以至千里，不积小流无以成江海</div></div><div class="card-info-data site-data is-center"><a href="/blog/archives/"><div class="headline">文章</div><div class="length-num">8</div></a><a href="/blog/tags/"><div class="headline">标签</div><div class="length-num">2</div></a><a href="/blog/categories/"><div class="headline">分类</div><div class="length-num">1</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/xxxxxx"><i class="fab fa-github"></i><span>Follow Me</span></a></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">This is my Blog</div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#Spring-%E7%9A%84%E4%BA%8B%E5%8A%A1"><span class="toc-number">1.</span> <span class="toc-text">Spring 的事务</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Spring%E6%94%AF%E6%8C%81%E7%9A%84%E4%BA%8B%E5%8A%A1%E7%AD%96%E7%95%A5"><span class="toc-number">1.1.</span> <span class="toc-text">Spring支持的事务策略</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8XML-Schema%E9%85%8D%E7%BD%AE%E4%BA%8B%E5%8A%A1%E7%AD%96%E7%95%A5"><span class="toc-number">1.2.</span> <span class="toc-text">使用XML Schema配置事务策略</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-Transactional"><span class="toc-number">1.3.</span> <span class="toc-text">使用@Transactional</span></a></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/06/22/SpringAOP/" title="SpringAOP"><img src="" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="SpringAOP"/></a><div class="content"><a class="title" href="/blog/2022/06/22/SpringAOP/" title="SpringAOP">SpringAOP</a><time datetime="2022-06-22T10:06:45.841Z" title="发表于 2022-06-22 18:06:45">2022-06-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/06/22/%E9%9C%80%E6%B1%82%E5%BC%80%E5%8F%91/" title="需求开发"><img src="" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="需求开发"/></a><div class="content"><a class="title" href="/blog/2022/06/22/%E9%9C%80%E6%B1%82%E5%BC%80%E5%8F%91/" title="需求开发">需求开发</a><time datetime="2022-06-22T10:06:45.837Z" title="发表于 2022-06-22 18:06:45">2022-06-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/06/22/%E6%B7%B1%E5%85%A5%E4%BD%BF%E7%94%A8%20Spring/" title="深入使用 Spring"><img src="" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="深入使用 Spring"/></a><div class="content"><a class="title" href="/blog/2022/06/22/%E6%B7%B1%E5%85%A5%E4%BD%BF%E7%94%A8%20Spring/" title="深入使用 Spring">深入使用 Spring</a><time datetime="2022-06-22T10:06:45.833Z" title="发表于 2022-06-22 18:06:45">2022-06-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/06/22/%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E7%9A%84%E6%9C%AC%E8%B4%A8/" title="软件需求的本质"><img src="" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="软件需求的本质"/></a><div class="content"><a class="title" href="/blog/2022/06/22/%E8%BD%AF%E4%BB%B6%E9%9C%80%E6%B1%82%E7%9A%84%E6%9C%AC%E8%B4%A8/" title="软件需求的本质">软件需求的本质</a><time datetime="2022-06-22T10:06:45.828Z" title="发表于 2022-06-22 18:06:45">2022-06-22</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/blog/2022/06/22/Spring%E5%85%A5%E9%97%A8/" title="Spring入门"><img src="" onerror="this.onerror=null;this.src='/blog/img/404.jpg'" alt="Spring入门"/></a><div class="content"><a class="title" href="/blog/2022/06/22/Spring%E5%85%A5%E9%97%A8/" title="Spring入门">Spring入门</a><time datetime="2022-06-22T10:06:45.824Z" title="发表于 2022-06-22 18:06:45">2022-06-22</time></div></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2022 By Milk Yang</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/blog/js/utils.js"></script><script src="/blog/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.js"></script><div class="js-pjax"></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>